use darling::FromDeriveInput;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as T2;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput};

use crate::periph::{dma::{self, Dir, Priority}, rcc::{self, RccAxB}, steal_periph, DmaInfo};

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(adc))]
pub struct Config {
    pub init: String,

    pub adc: String,

    #[darling(multiple)]
    pub ch: Vec<u8>,
}

impl Config {

    pub fn build(&self) -> T2 {
        let steal_adc = steal_periph(&self.adc);
        let init_fn = format_ident!("{}", self.init);
        let field = format_ident!("{}en", self.adc.to_lowercase());
        let rcc_init = rcc::rcc_enable(RccAxB::APB2, &field, false);

        let mut sqr3s = Vec::new();
        let mut sqr2s = Vec::new();
        let mut sqr1s = Vec::new();
        let mut smpr1s = Vec::new();
        let mut smpr2s = Vec::new();

        for (i, c) in self.ch.iter().enumerate() {
            let index = i + 1;
            let ch = *c;
            let sq = format_ident!("sq{}", index);
            let exp = quote! {
                #sq().bits(#ch)
            };
            match index {
                1..=6 => {
                    //sqr3
                    sqr3s.push(exp);
                },
                7..=12 => {
                    //sqr2
                    sqr2s.push(exp);
                },
                13..=16 => {
                    //sqr1
                    sqr1s.push(exp);
                }
                _ => panic!("invalid channel number")
            };
            let smp = format_ident!("smp{}", ch);
            let exp = quote! {
                #smp().cycles480()
            };
            match ch {
                0..=9 => {
                    // smpr2
                    smpr2s.push(exp);
                },
                10..=18 => {
                    // smpr1
                    smpr1s.push(exp);
                },
                _ => panic!("invalid ch")
            }
        }
        let count = self.ch.len() as u8 - 1;
        sqr1s.push(quote! {
            l().bits(#count)
        });

        let mut expand = quote! {
            let adc = #steal_adc;
            adc.cr1().write(|w| w.scan().enabled());
            adc.cr2().write(|w| { 
                w.dma().enabled()
                .dds().continuous()
                .cont().continuous()
            });
            adc.sqr1().write(|w| unsafe { 
                w #(.#sqr1s)*
            });
        };
        if !sqr2s.is_empty() {
            expand.extend(quote! {
                adc.sqr2().write(|w| unsafe { 
                    w #(.#sqr2s)*
                });
            });
        }
        if !sqr3s.is_empty() {
            expand.extend(quote! {
                adc.sqr3().write(|w| unsafe { 
                    w #(.#sqr3s)*
                });
            });
        }
        if !smpr1s.is_empty() {
            expand.extend(quote! {
                adc.smpr1().write(|w| unsafe {
                    w #(.#smpr1s)*
                });
            });
        }
        if !smpr2s.is_empty() {
            expand.extend(quote! {
                adc.smpr2().write(|w| unsafe {
                    w #(.#smpr2s)*
                });
            });
        }
        expand.extend(quote! {
            adc.cr2().modify(|_, w| w.adon().enabled().swstart().start());
        });

        let info = DmaInfo::find_dma(&self.adc).unwrap();
        let adc_dma = dma::Config {
            init: format!("{}_dma_init", self.adc.to_lowercase()),
            dma: info.dma,
            stream: info.stream as usize,
            ch: info.ch,
            priority: Priority::Low,
            minc: true,
            circ: true,
            dir: Dir::PeriphToMem,
            tcie: false,
            size: 16,
        };
        let adc_expand = adc_dma.build();

        quote! {
            #adc_expand

            #[inline(always)]
            pub fn #init_fn() {
                #rcc_init
                #expand
            }

            #[inline(always)]
            pub fn adc_dma_par() -> u32 {
                let adc = #steal_adc;
                adc.dr().as_ptr() as u32
            }
        }
    }
}

pub fn build(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let config = Config::from_derive_input(&input).unwrap();
    let build = config.build();
    let expand = quote! {
        impl #name {
            #build
        }
    };
    expand.into()
}


